package com.easylinkin.linkappapi.bim.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easylinkin.bases.redis.util.RedisUtil;
import com.easylinkin.core.models.Operator;
import com.easylinkin.linkappapi.bim.bo.TaskNode;
import com.easylinkin.linkappapi.bim.dto.BindingPlanDto;
import com.easylinkin.linkappapi.bim.entity.BimProjectImportRecord;
import com.easylinkin.linkappapi.bim.entity.BimServerConfig;
import com.easylinkin.linkappapi.bim.entity.BimTask;
import com.easylinkin.linkappapi.bim.entity.BimTaskCorrelation;
import com.easylinkin.linkappapi.bim.mapper.BimServerConfigMapper;
import com.easylinkin.linkappapi.bim.mapper.BimTaskMapper;
import com.easylinkin.linkappapi.bim.service.BimTaskCorrelationService;
import com.easylinkin.linkappapi.bim.service.BimTaskService;
import com.easylinkin.linkappapi.bim.vo.BimTaskVo;
import com.easylinkin.linkappapi.common.exceptions.BusinessException;
import com.easylinkin.linkappapi.common.exceptions.ExceptionEnum;
import com.easylinkin.linkappapi.util.CollectionUtils;
import net.sf.mpxj.MPXJException;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.Task;
import net.sf.mpxj.mpp.MPPReader;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.easylinkin.linkappapi.bim.controller.BimScheduleController.DATE_FORMAT;

/**
 * bim项目任务（计划）表(BimTask)表服务实现类
 *
 * @author pwqoc
 * @since 2024-05-24 15:02:33
 */
@Service("bimTaskService")
public class BimTaskServiceImpl extends ServiceImpl<BimTaskMapper, BimTask> implements BimTaskService {
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private BimTaskCorrelationService taskCorrelationService;
    @Resource
    private BimServerConfigMapper bimServerConfigMapper;

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void importProject(MultipartFile file, BimProjectImportRecord projectImportRecord) {

        /*
        读取文件
         */
        ProjectFile pf;
        try {
            pf = new MPPReader().read(file.getInputStream());
        } catch (IOException | MPXJException e) {
            ExceptionEnum.CommonExceptionEnum fileReadError = ExceptionEnum.CommonExceptionEnum.FILE_READ_ERROR;
            throw new BusinessException(fileReadError.getErrorCode(), fileReadError.getErrorMsg() + e.getMessage());
        }

        /*
        解析任务
         */
        List<BimTask> taskList = pf.getTasks().stream().filter(e -> e.getID() != 0 && e.getParentTask() != null).map(e -> {
            BimTask task = new BimTask();
            task.setId(IdUtil.getSnowflake().nextIdStr());
            task.setImportRecordId(projectImportRecord.getId());
            task.setTaskId(e.getID());
            task.setTaskPid(e.getParentTask() == null ? null : e.getParentTask().getID());
            task.setLevel(this.getTaskLevel(e));
            task.setTaskName(e.getName());
            task.setTaskFullName(this.getTaskFullName(e));
            task.setStartDate(new DateTime(e.getStart()));
            task.setEndDate(new DateTime(e.getFinish()));
            task.setDuration();
            task.setTenantId(projectImportRecord.getTenantId());
            task.setCreatorId(projectImportRecord.getCreatorId());
            task.setCreator(projectImportRecord.getCreator());
            task.setCreateTime(projectImportRecord.getCreateTime());
            task.setModifyId(projectImportRecord.getModifyId());
            task.setModifier(projectImportRecord.getModifier());
            task.setModifyTime(projectImportRecord.getModifyTime());
            return task;
        }).collect(Collectors.toList());

        Assert.isTrue(CollectionUtils.convertSet(taskList, BimTask::getTaskFullName).size() == taskList.size(),
                () -> "文件中的任务计划重复");

        Assert.isTrue(this.saveBatch(taskList), () -> "保存失败");
    }

    @Override
    public List<TaskNode> tree(String importRecordId, String taskName, String tenantId) {
        List<BimTask> list = this.list(new LambdaQueryWrapper<BimTask>()
                .eq(BimTask::getImportRecordId, importRecordId)
                .eq(BimTask::getTenantId, tenantId)
                .like(StrUtil.isNotBlank(taskName), BimTask::getTaskName, taskName));
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        List<BimTaskCorrelation> taskCorrelationList = taskCorrelationService.list(new LambdaQueryWrapper<BimTaskCorrelation>()
                .eq(BimTaskCorrelation::getTenantId, tenantId)
                .in(BimTaskCorrelation::getTaskFullName, CollectionUtils.convertSet(list, BimTask::getTaskFullName))
        );
        Map<String, String> map = CollectionUtils.convertMap(taskCorrelationList, BimTaskCorrelation::getTaskFullName, BimTaskCorrelation::getComponentId);

        HashMap<Integer, TaskNode> nodeMap = new HashMap<>();
        List<TaskNode> taskNodeList = CollectionUtils.convertList(list, e -> {
            TaskNode node = new TaskNode();
            node.setId(e.getTaskId());
            node.setParentId(e.getTaskPid());
            node.setName(e.getTaskName());
            node.setStartDate(DateUtil.format(e.getStartDate(), DATE_FORMAT));
            node.setEndDate(DateUtil.format(e.getEndDate(), DATE_FORMAT));
            node.setDuration(e.getDuration());
            node.setCompletionStatus(e.getCompletionStatus());
            node.setRid(e.getId());
            node.setComponentId(map.get(e.getTaskFullName()));
            nodeMap.put(node.getId(), node);
            return node;
        });
        return TaskNode.tree(taskNodeList, 1, nodeMap);
    }

    @Override
    public void bindingPlan(BindingPlanDto dto, String tenantId) {
        BimTask task = this.getOne(new LambdaQueryWrapper<BimTask>()
                .eq(BimTask::getId, dto.getRid())
                .eq(BimTask::getTenantId, tenantId)
                .eq(BimTask::getImportRecordId, dto.getImportRecordId())
        );
        Assert.notNull(task, () -> "该任务计划不存在");
        Assert.hasText(task.getTaskFullName(), () -> "该任务计划异常");
        BimTaskCorrelation taskCorrelation = new BimTaskCorrelation();
        taskCorrelation.setTaskFullName(task.getTaskFullName());
        taskCorrelation.setComponentId(dto.getComponentId());
        taskCorrelation.setType(dto.getType());
        taskCorrelation.setTitle(dto.getTitle());
        taskCorrelation.setTenantId(tenantId);
        BimTaskCorrelation one = taskCorrelationService.getOne(new LambdaQueryWrapper<BimTaskCorrelation>()
                .eq(BimTaskCorrelation::getTenantId, tenantId)
                .eq(BimTaskCorrelation::getTaskFullName, task.getTaskFullName()));
        if (one == null) {
            Assert.isTrue(taskCorrelationService.save(taskCorrelation), () -> "绑定失败");
        }else {
            taskCorrelation.setTaskFullName(null);
            Assert.isTrue(taskCorrelationService.update(taskCorrelation, new LambdaQueryWrapper<BimTaskCorrelation>()
                    .eq(BimTaskCorrelation::getTaskFullName, task.getTaskFullName())), () -> "绑定失败");
        }
    }

    @Override
    public String getBimfaceViewToken(Operator operator) {
        BimServerConfig bimServerConfig = bimServerConfigMapper.selectOne(new LambdaQueryWrapper<BimServerConfig>()
                .eq(BimServerConfig::getTenantId, operator.getTenantId()));
        Assert.notNull(bimServerConfig, () -> "请先配置Bimfacef服务参数");
        String fileId = bimServerConfig.getFileId();
        Assert.hasText(fileId, () -> "文件id不能为空");
        String key = "bimface:view_token" + fileId;
        Object o = redisUtil.get(key);
        String token = o == null ? null : (String) o;

        if (StrUtil.isNotBlank(token)) {
            return token;
        }
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization ", "Bearer "+ this.getBimfaceAccessToken(bimServerConfig.getAppKey(), bimServerConfig.getAppSecret()));
        String body = HttpUtil.createGet("https://api.bimface.com/view/token").addHeaders(headers).form("fileId", fileId)
                .execute().body();
        JSONObject jsonObject = JSON.parseObject(body);
        if (jsonObject == null || StrUtil.isBlank(jsonObject.getString("data"))) {
            throw new BusinessException(ExceptionEnum.CommonExceptionEnum.EXCEPTION_CODE_500.getErrorCode(), "bimface server return error");
        }
        token = jsonObject.getString("data");
        redisUtil.set(key, token);
        redisUtil.expire(key, 719, TimeUnit.MINUTES);
        return token;
    }

    @Override
    public List<BimTaskVo> bindingRelationship(String importRecordId, String tenantId) {
        List<BimTask> list = this.list(new LambdaQueryWrapper<BimTask>()
                        .eq(BimTask::getTenantId, tenantId)
                .eq(BimTask::getImportRecordId, importRecordId)
        );
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        List<BimTaskCorrelation> taskCorrelationList = taskCorrelationService.list(new LambdaQueryWrapper<BimTaskCorrelation>()
                .eq(BimTaskCorrelation::getTenantId, tenantId)
                .in(BimTaskCorrelation::getTaskFullName, CollectionUtils.convertSet(list, BimTask::getTaskFullName))
        );
        Map<String, BimTaskCorrelation> map = CollectionUtils.convertMap(taskCorrelationList, BimTaskCorrelation::getTaskFullName);

        return list.stream().map(e -> {
            BimTaskVo vo = new BimTaskVo();
            vo.setTaskId(e.getTaskId());
            BimTaskCorrelation taskCorrelation = map.get(e.getTaskFullName());
            if (taskCorrelation != null) {
                vo.setRid(e.getId());
                vo.setComponentId(taskCorrelation.getComponentId());
                vo.setType(taskCorrelation.getType());
                vo.setTitle(taskCorrelation.getTitle());
            }
            vo.setTaskName(e.getTaskName());
            return vo;
        }).filter(e -> StrUtil.isNotBlank(e.getComponentId())).collect(Collectors.toList());
    }

    /*
    任务层级
     */
    private int getTaskLevel(Task t) {
        if (t == null || t.getParentTask() == null) {
            // 根任务的层级为0
            return 0;
        } else {
            // 父任务的层级加1
            return 1 + getTaskLevel(t.getParentTask());
        }
    }


    /*
    任务全称
    */
    private String getTaskFullName(Task t) {
        if (t == null || t.getID() == 0) {
            // 根任务的层级为0
            return "";
        } else if (t.getParentTask() == null) {
            return t.getName();
        } else {
            // 父任务的层级加1
            return getTaskFullName(t.getParentTask()) + t.getName();
        }
    }

    private String getBimfaceAccessToken(String appKey, String appSecret) {
        String key = "bimface:access_token";
        Object o = redisUtil.get(key);
        String token = o == null ? null : (String) o;
        if (StrUtil.isNotBlank(token)) {
            return token;
        }
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization ", "Basic" + " " + Base64.getEncoder().encodeToString((appKey + ":" + appSecret).getBytes(StandardCharsets.UTF_8)));
        String body = HttpUtil.createPost("https://api.bimface.com/oauth2/token").addHeaders(headers)
                .execute().body();
        JSONObject jsonObject = JSON.parseObject(body);
        if (jsonObject == null || !StrUtil.equals(jsonObject.getString("code"), "success")) {
            throw new BusinessException(ExceptionEnum.CommonExceptionEnum.EXCEPTION_CODE_500.getErrorCode(), "bimface server return error");
        }
        jsonObject = jsonObject.getJSONObject("data");
        Assert.notNull(jsonObject, () -> "bimface server return null");

        token = jsonObject.getString("token");
        String expireTime = jsonObject.getString("expireTime");
        DateTime expDate = DateUtil.parse(expireTime, "yyyy-MM-dd HH:mm:ss");
        redisUtil.set(key, token);
        redisUtil.expireAt(key, DateUtil.offsetMinute(expDate, -1));
        return token;
    }

}

